Api et données spatio-temporelles avec Python#
Sujet |
Auteur |
Dernière Màj |
|---|---|---|
Api et données spatio-temporelles |
Thomas LEYSENS (IR AME) |
23 Juin 2023 |
Enjeux & Propositions#
Enjeu |
Proposition |
|---|---|
Global |
Montrer des exemples d’acquisition & de visualisation de données |
Utiliser une Api |
API températures de l’eau & API Geo communes |
Requête OpenStreetMap |
API Overpass (OpenStreetMap) |
Cartographier des données |
Utiliser les possibilités de base de GeoPandas |
Représenter des données |
Utilisation de la librairie Bokeh |
Aperçu classe et méthode Python |
Montrer rapidement des exemples de classes et méthodes Python |
AVERTISSEMENT: CE TUTORIEL VISE A MONTRER CERTAINES POSSIBILITES D’ACQUISITION DE DONNEES VIA DES API ET PYTHON. IL NE CHERCHE PAS A APPRENDRE AUX NOVICES A CODER EN PYTHON (néanmoins, il contient du code réutilisable et peut aider les développeurs Python et/ou à présenter des opportunités pour les chercheurs/ingénieurs/techniciens et les curieux)
Introduction#
Nous sommes en juin, il fait chaud.
J’ai donc tout naturellement envie d’aller tremper mes pieds dans de l’eau fraîche. Et cela, n’importe où en France car j’ai la capacité de me téléporter (j’ai découvert et maîtrisé un trou de ver mais, chuuuuuuut, c’est un secret, donc je ne peux pas vous expliquer le pourquoi du comment et, si la NSA vous demande, je n’existe pas !).
Vous me direz: il suffit de me rendre à une piscine du coin ou à un lac ou une rivière proche.
Trop simple ! J’ai des exigences très précises, je ne laisse rien au hasard ! Je souhaite connaître l’heure propice pour une température idéale et je ne veux croiser personne car j’ai des pieds de Hobbit. Je souhaite également faire des randonnées en montagne, donc je veux obtenir des informations sur les chemins pédestres et les points d’eau potable.
Je veux donc avoir des données sur:
la température au cours de la journée
les chemins de randonnées et les points d’eau potable pour une zone précise
Et je veux pouvoir:
obtenir ces données automatiquement
changer mes requêtes au besoin
réutiliser mes procédures
visualiser les données
me passer totalement de logiciels payants et propriétaires
utiliser des données open source et exploitables comme je le souhaite
Petit conseil, en passant, pour protéger votre vie privée et ne pas la donner entièrement aux GAFAM: vous pouvez utiliser des applications respectueuses de la vie privée (et basées sur des données “libres” comme celles d’OpenStreetMap) pour vous orienter (notamment à pied et à vélo car la version voiture est encore en beta au moment où sont écrites ces lignes) comme Organic Maps (lien App Store, lien Google Play, lien F-Droid). Cette application peut fonctionner sans réseau mobile si vous avez téléchargé les données au préalable. Et ces données peuvent être plus précises, parfois, que celles des GAFAM, car ces dernières ne cartographient pas les lieux qui n’ont pas d’intérêt économique pour elles alors que les données OSM sont construites par des contributeurs volontaires partout dans le monde.
Trêve de bavardages, allons chercher des données !
Une recherche rapide sur un moteur de recherche alternatif à Google (car oui Google récupère également des informations sur vous de cette manière) comme DuckDuckGo nous met sur la piste des données libres de Hub’eau concernant les températures de l’eau et plus particulièrement cette interface de programmation plus communément appelée API (Application Programming Interface), soit “[…] un ensemble normalisé de classes, de méthodes, de fonctions et de constantes qui sert de façade par laquelle un logiciel offre des services à d’autres logiciels. Elle est offerte par une bibliothèque logicielle ou un service web, le plus souvent accompagnée d’une description qui spécifie comment des programmes « consommateurs » peuvent se servir des fonctionnalités du programme « fournisseur ».” (source: Wikipedia)
Récupérer une bounding box pour obtenir la liste complète des stations#
Lorsqu’on explore la documentation de l’API “températures” de Hub’eau, on constate qu’il est possible d’obtenir une liste des stations à partir d’une zone géographique (appelée bbox, contraction de bounding box)


Nous avons donc besoin de valeurs pour cette bounding box. Il faut noter que la projection des coordonnées doit être WGS84 (EPSG 4326). Nous ne détaillerons pas ici les projections cartographiques mais, si cela vous intéresse, commencez par lire cette page Wikipedia et/ou celle-ci.
Pour obtenir ces valeurs, il existe, entre autres, des outils comme le Tile Calculator de Geofabrik.

Ensuite, nous allons utiliser la librairie Python Requests pour interroger l’API avec des paramètres précis et obtenir des données au format GeoJSON.
Nous allons utiliser une requête de type GET.
Pour ce faire, la méthode get de requests demande une URL ainsi que des paramètres (correspondant aux paramètres proposés par l’API Températures de Hub’eau):
url: “https://hubeau.eaufrance.fr/api/v1/temperature/station”paramètres:
bbox: [-1.8888, 42.3218, 7.7906, 46.441] => obtenue depuis l’outil Tile Calculator et formatée correctementformat: “geojson” => on souhaite obtenir des données au format GeoJSON
import requests
url = "https://hubeau.eaufrance.fr/api/v1/temperature/station"
bbox = [-1.8888, 42.3218, 7.7906, 46.441]
params = {
"bbox":bbox,
"format":"geojson"
}
response = requests.get(
url,
params=params
)
Une fois la requête exécutée, vérifions que tout s’est bien passé en consultant le status_code
response.status_code
200
Le code 200 indique qu’aucune erreur ne s’est produite.
Attention cela ne signifie pas pour autant qu’il y a un résultat valide. Ce code peut s’accompagner d’un résultat “vide” si la requête n’a engendré aucune erreur mais que les paramètres de celle-ci ne correspondent à aucune donnée
Jetons un oeil aux 2 premiers résultats en consultant la partie features de la réponse (elle a la structure d’une liste de dictionnaires au sens Python):
response.json()["features"][:2]
[{'type': 'Feature',
'properties': {'altitude': 456.0,
'code_cours_eau': 'V1230560',
'code_bassin': None,
'latitude': 45.78876059022991,
'libelle_cours_eau': None,
'libelle_sous_bassin': None,
'code_commune': '74104',
'code_sous_bassin': None,
'code_region': '84',
'code_type_projection': 26,
'code_troncon_hydro': 'V1230560',
'coordonnee_x': 950959.0,
'coordonnee_y': 6526151.0,
'longitude': 6.231161741514297,
'uri_masse_eau': 'http://id.eaufrance.fr/MasseDEauRiviere_VEDL2019/DR535',
'libelle_departement': 'Haute-Savoie',
'libelle_station': 'EAU MORTE A DOUSSARD 2',
'code_departement': '74',
'uri_station': 'https://id.eaufrance.fr/StationMesureEauxSurface/06830079',
'localisation': 'Pont D 909a',
'code_masse_eau': 'DR535',
'code_station': '06830079',
'uri_bassin': None,
'libelle_bassin': None,
'date_maj_infos': '2022-12-16',
'libelle_commune': 'Doussard',
'uri_cours_eau': 'https://id.eaufrance.fr/CoursEau_Carthage2017/V1230560',
'pk': None,
'libelle_region': 'Auvergne-Rhône-Alpes',
'libelle_masse_eau': None},
'geometry': {'type': 'Point',
'crs': {'type': 'name',
'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
'coordinates': [6.231161741514297, 45.78876059022991]}},
{'type': 'Feature',
'properties': {'altitude': 1036.0,
'code_cours_eau': 'W2210500',
'code_bassin': None,
'latitude': 44.73681825126893,
'libelle_cours_eau': None,
'libelle_sous_bassin': None,
'code_commune': '05139',
'code_sous_bassin': None,
'code_region': '93',
'code_type_projection': 26,
'code_troncon_hydro': 'W2210500',
'coordonnee_x': 928992.0,
'coordonnee_y': 6408383.0,
'longitude': 5.8930717241678705,
'uri_masse_eau': 'http://id.eaufrance.fr/MasseDEauRiviere_VEDL2019/DR348',
'libelle_departement': 'Hautes-Alpes',
'libelle_station': 'SOULOISE A ST-DISDIER-EN-DEVOLUY ',
'code_departement': '05',
'uri_station': 'https://id.eaufrance.fr/StationMesureEauxSurface/06820164',
'localisation': '200 m aval St Disdier en Dévoluy, 50 m aval pont des Jouves',
'code_masse_eau': 'DR348',
'code_station': '06820164',
'uri_bassin': None,
'libelle_bassin': None,
'date_maj_infos': '2022-12-16',
'libelle_commune': 'Dévoluy',
'uri_cours_eau': 'https://id.eaufrance.fr/CoursEau_Carthage2017/W2210500',
'pk': None,
'libelle_region': "Provence-Alpes-Côte d'Azur",
'libelle_masse_eau': None},
'geometry': {'type': 'Point',
'crs': {'type': 'name',
'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
'coordinates': [5.8930717241678705, 44.73681825126893]}}]
Nous avons bien obtenu des stations avec leurs différentes propriétés dont la localisation (dans les parties geometry).
Mais ce n’est pas facilement lisible et exploitable. Transformons donc ces résultats en tableau et, plus précisément, en GeoDataFrame.
import geopandas as gpd
gdf_stations = gpd.GeoDataFrame.from_features(
response.json()["features"]
).set_crs(
epsg=4326
)
gdf_stations
| geometry | altitude | code_cours_eau | code_bassin | latitude | libelle_cours_eau | libelle_sous_bassin | code_commune | code_sous_bassin | code_region | ... | code_masse_eau | code_station | uri_bassin | libelle_bassin | date_maj_infos | libelle_commune | uri_cours_eau | pk | libelle_region | libelle_masse_eau | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | POINT (6.23116 45.78876) | 456.0 | V1230560 | None | 45.788761 | None | None | 74104 | None | 84 | ... | DR535 | 06830079 | None | None | 2022-12-16 | Doussard | https://id.eaufrance.fr/CoursEau_Carthage2017/... | NaN | Auvergne-Rhône-Alpes | None |
| 1 | POINT (5.89307 44.73682) | 1036.0 | W2210500 | None | 44.736818 | None | None | 05139 | None | 93 | ... | DR348 | 06820164 | None | None | 2022-12-16 | Dévoluy | https://id.eaufrance.fr/CoursEau_Carthage2017/... | NaN | Provence-Alpes-Côte d'Azur | None |
| 2 | POINT (4.53664 45.41307) | 588.0 | V31-0400 | None | 45.413072 | None | None | 42322 | None | 84 | ... | DR2019 | 06820138 | None | None | 2022-12-16 | La Valla-en-Gier | https://id.eaufrance.fr/CoursEau_Carthage2017/... | NaN | Auvergne-Rhône-Alpes | None |
| 3 | POINT (5.69218 45.01776) | 351.0 | W2--0200 | None | 45.017756 | None | None | 38545 | None | 84 | ... | DR337 | 06820118 | None | None | 2022-12-16 | Vif | https://id.eaufrance.fr/CoursEau_Carthage2017/... | NaN | Auvergne-Rhône-Alpes | None |
| 4 | POINT (6.30392 45.04346) | 1428.0 | W27-0400 | None | 45.043456 | None | None | 05063 | None | 93 | ... | DR336 | 06820089 | None | None | 2022-12-16 | La Grave | https://id.eaufrance.fr/CoursEau_Carthage2017/... | NaN | Provence-Alpes-Côte d'Azur | None |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 348 | POINT (4.15590 45.49659) | 0.0 | K06-0310 | None | 45.496587 | None | None | 42256 | None | 84 | ... | GR0166 | 04009350 | None | None | 2013-05-27 | Saint-Marcellin-en-Forez | https://id.eaufrance.fr/CoursEau_Carthage2017/... | 979748.0 | Auvergne-Rhône-Alpes | None |
| 349 | POINT (4.52558 45.62799) | 0.0 | K06-0330 | None | 45.627989 | None | None | 69110 | None | 84 | ... | GR0167A | 04009050 | None | None | 2013-05-27 | Larajasse | https://id.eaufrance.fr/CoursEau_Carthage2017/... | 961105.0 | Auvergne-Rhône-Alpes | None |
| 350 | POINT (4.11411 45.28556) | 0.0 | K05-0300 | None | 45.285563 | None | None | 43025 | None | 84 | ... | GR0163B | 04003900 | None | None | 2009-10-23 | Beauzac | https://id.eaufrance.fr/CoursEau_Carthage2017/... | 999190.0 | Auvergne-Rhône-Alpes | None |
| 351 | POINT (3.89092 45.37977) | 0.0 | K05-0300 | None | 45.379774 | None | None | 63412 | None | 84 | ... | GR0163A | 04003645 | None | None | 2013-02-19 | Sauvessanges | https://id.eaufrance.fr/CoursEau_Carthage2017/... | 959883.0 | Auvergne-Rhône-Alpes | None |
| 352 | POINT (4.33156 45.22006) | 0.0 | K0454000 | None | 45.220056 | None | None | 43087 | None | 84 | ... | GR0162 | 04003355 | None | None | 2010-06-07 | Dunières | https://id.eaufrance.fr/CoursEau_Carthage2017/... | 979392.0 | Auvergne-Rhône-Alpes | None |
353 rows × 31 columns
Les données au format GeoDataFrame sont plus facilement exploitables, manipulables et peuvent être enregistrées sous différements formats (GeoPackage, GeoJSON, Shapefiles) lisibles sous QGIS. Les géométries peuvent être - si besoin - transformées et exportées au format CSV pour être lues dans un logiciel de tableurs externe.
On peut également les visualiser dans l’espace dans une carte interactive au sein de ce Notebook en utilisant la méthode explore
gdf_stations.explore(
color="red",
marker_kwds={
"radius":5
},
tiles="Stamen Terrain"
)
Choix d’un numéro de station#
En zoomant du côté de la région Rhône-Alpes, nous survolons le cercle rouge dans la commune de Saint-Christophe-en-Oisans, ce qui nous permet d’obtenir une popup et de récupérer le code_station (il faut noter que nous pourrions également obtenir cette information en parcourant ou filtrant le GeoDataFrame mais il s’agit ici de montrer les possibilités d’exploration visuelle et cartographique).

Ce code va nous permettre de récupérer les mesures de températures au sein de cette station via l’API température.

Nous allons récupérer des données sur une année pour avoir une estimation (très grossière car l’idée n’est pas d’être précis mais de montrer les possibilités) de la période “idéale” pour avoir une eau à une température souhaitée.
Pour cela, nous allons utiliser les paramètres - proposés par l’API - suivants:
date_debut_mesuredate_fin_mesurecode_stationsizeformat
Création d’une classe et d’une méthode Python#
Afin d’être plus efficace et de créer du code réutilisable, il convient de créer des classes et des méthodes Python. J’ai donc créé du code paramétrable dans un nouveau module Python importable dans ce Notebook. Nous n’aborderons pas le développement de code Python dans ce tutoriel, mais vous pouvez consulter/utiliser/modifier librement le module api_request.request si vous le souhaitez, étant donné la license BSD 3-Clause du dépôt.
Nous importons la classe GdfFromApi du module api_request.request. Cette classe permet de faire une requête GET avec des paramètres précis et d’obtenir un GeoDataFrame avec les résultats.
# Exécuter cette cellule pour avoir accès au module local api_request
import os
import sys
module_path = os.path.abspath(
os.path.join(
".."
)
)
if module_path not in sys.path:
sys.path.append(module_path)
from api_request.request import GdfFromApi #import de la classe
base_url = "https://hubeau.eaufrance.fr/api"
sub_url = "/v1/temperature/chronique"
params = {
"date_debut_mesure":"2015-01-01",
"date_fin_mesure":"2015-12-31",
"code_station":"06143650",
"size":20000,
"format":"geojson"
}
# Instanciation de la classe
get_api = GdfFromApi(base_url)
# Exécution de la méthode get
gdf_temperature = get_api.get(
sub_url,
params
)
print (
"Status code: {}".format(
get_api.status_code
)
)
gdf_temperature
Status code: 200
| geometry | libelle_station | code_cours_eau | uri_station | latitude | libelle_cours_eau | code_station | code_commune | resultat | code_parametre | code_unite | libelle_commune | code_qualification | libelle_qualification | date_mesure_temp | uri_cours_eau | libelle_parametre | heure_mesure_temp | symbole_unite | longitude | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 0.051 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-01-01 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 00:00:00 | °C | 6.154849 |
| 1 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 0.051 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-01-01 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 01:00:00 | °C | 6.154849 |
| 2 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 0.051 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-01-01 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 02:00:00 | °C | 6.154849 |
| 3 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 0.051 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-01-01 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 03:00:00 | °C | 6.154849 |
| 4 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 0.051 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-01-01 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 04:00:00 | °C | 6.154849 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8754 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 3.327 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-12-31 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 19:00:00 | °C | 6.154849 |
| 8755 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 3.327 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-12-31 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 20:00:00 | °C | 6.154849 |
| 8756 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 3.327 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-12-31 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 21:00:00 | °C | 6.154849 |
| 8757 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 3.301 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-12-31 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 22:00:00 | °C | 6.154849 |
| 8758 | POINT (6.15485 44.96755) | VENEON A ST-CHRISTOPHE-EN-OISANS 1 | W2730500 | https://id.eaufrance.fr/StationMesureEauxSurfa... | 44.967547 | None | 06143650 | 38375 | 3.301 | 1301 | 27 | Saint-Christophe-en-Oisans | 4 | Non qualifié | 2015-12-31 | https://id.eaufrance.fr/CoursEau_Carthage2017/... | Température de l'Eau | 23:00:00 | °C | 6.154849 |
8759 rows × 20 columns
Les températures sont effectuées par heure et sont nombreuses.
Le champ contenant les dates
date_mesure_tempet le champ contenant les heuresheure_mesure_tempsont séparés et sont au formatstring. Il est donc nécessaire de les regrouper et de les transformer endatetimepour que cela soit plus pratique à exploiter et à visualiser.
from datetime import datetime
# Désactiver des avertissements qui n'ont pas lieu d'être ici
gpd.pd.options.mode.chained_assignment = None
# Regroupements des champs "date_mesure_temp" et "heure_mesure_temp"
gdf_temperature["datetime"] = gdf_temperature.apply(
lambda x: "{} {}".format(
x["date_mesure_temp"],
x["heure_mesure_temp"]
),
axis=1
)
# Application de la transformation en datetime
gdf_temperature["datetime"] = [
datetime.strptime(
x,
"%Y-%m-%d %H:%M:%S"
) for x in gdf_temperature[
"datetime"
]
]
# Observation des 3 premiers résultats du nouveau champ
gdf_temperature["datetime"].head(3)
0 2015-01-01 00:00:00
1 2015-01-01 01:00:00
2 2015-01-01 02:00:00
Name: datetime, dtype: datetime64[ns]
Représenter/visualiser les résultats#
Observons les résultats sous forme de graphique afin d’en avoir une approche plus globale et visuelle.
Version simple et statique#
Essayons tout d’abord une visualisation statique en utilisant la méthode plot de GeoPandas
# Détermination du titre en récupérant la première valeur
# observée dans le champ "libelle_station"
title = "Températures (Celsius) à la station {}".format(
gdf_temperature["libelle_station"][0]
)
# Titre de l'axe Y
ylabel = "Température en degrés Celsius"
# Titre de l'axe X
xlabel = "Date & heure de la mesure"
# Création du graphique
gdf_temperature.plot(
x="datetime", # champ pour X
y="resultat", # champ pour Y
kind="scatter", # forme
figsize=(20,4), # largeur et hauteur en pouces
title=title, # titre
ylabel=ylabel, # label Y
xlabel=xlabel # label X
)
<Axes: title={'center': 'Températures (Celsius) à la station VENEON A ST-CHRISTOPHE-EN-OISANS 1'}, xlabel='Date & heure de la mesure', ylabel='Température en degrés Celsius'>
Cette version statique est un premier pas mais elle est limitée si l’on souhaite obtenir des détails.
Passons donc à une version améliorée et interactive.
Version plus développée et interactive#
Pour ce faire, nous utilisons la librairie Bokeh dans sa version Python.
Nous n’entrerons pas dans les détails, mais voici quelques liens sur les méthodes et outils utilisés:
colormappervoir cet exempleDatetimeTickFormatter, voir la documentationcolor_bar, voir cet exempleHover tool, voir cet exemple
Aperçu rapide des outils de la figure#

import math
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.transform import linear_cmap
from bokeh.models import DatetimeTickFormatter, HoverTool, ColumnDataSource
output_notebook() # Obtenir la sortie graphique au sein du notebook
# Paramètres de la figure (en reprenant certains éléments précédents)
p = figure(
width=1050, # largeur en pixels
height=600, # hauteur en pixels
title=title,
y_axis_label=ylabel,
x_axis_label=xlabel
)
# Création d'une table linéaire de couleurs
cmap = linear_cmap(
"resultat", # Champ des valeurs
palette="Plasma256", # Choix palette
low=gdf_temperature["resultat"].min(), # Minimum
high=gdf_temperature["resultat"].max() # Maximum
)
# Création de la source pour le graphique
# soit le GeoDataFrame sans la géométrie
source = ColumnDataSource(
gdf_temperature.drop(
columns=["geometry"]
)
)
# Ajout de points pour chaque mesure/résultat
c = p.circle(
"datetime", # Champ pour X
"resultat", # Champ pour Y
source=source, # Source
color=cmap # Couleur déterminée par la table de couleur
)
# Construction d'une barre de couleur basée sur les glyphes précédents
color_bar = c.construct_color_bar(width=20)
# Ajout de cette barre à la figure
p.add_layout(color_bar, "right")
# Formatage de l'axe X pour représenter correctement les dates
p.xaxis.formatter = DatetimeTickFormatter(
minutes="%Y-%m-%d %H:%M:%S",
hours="%Y-%m-%d %H:%M:%S",
days="%Y-%m-%d",
months="%Y-%m-%d",
years="%Y-%m-%d"
)
# Création et ajout de l'outil de survol (hover)
hover = HoverTool(
tooltips = [
("Date", "@datetime{%Y-%m-%d %H:%M:%S}"),
("Température", "@resultat")
],
formatters={
"@datetime": "datetime"
},
)
p.add_tools(hover)
# Orientation du champ X pour plus de lisibilité
p.xaxis.major_label_orientation = math.pi/4
# Visualisation
show(p)
Trouver la période pour une température “idéale”#
Pour faire cette estimation très grossière, nous allons:
déterminer une valeur de température minimale
déterminer une valeur de température maximale
filtrer le GeoDataFrame en fonction de ces 2 valeurs
déterminer l’heure médiane
obtenir la date minimale et la date maximale pour cette heure médiane
présenter un résultat
import locale
from statistics import median
# Fixer les locales (pour obtenir des dates au format "français")
locale.setlocale(locale.LC_TIME, "fr_FR.UTF-8")
# Déterminer la température minimale et la température maximale
min_temp = 11.5
max_temp = 12
# Filtrer le GeoDataFrame et retenir la Série "datetime"
times = gdf_temperature.loc[
(
gdf_temperature["resultat"]>=min_temp
) & (
gdf_temperature["resultat"]<=max_temp
)
]["datetime"]
# Déterminer l'heure médiane
perfect_hour = median(
[
x.time() for x in times
]
)
# Récupérer la date "minimale" et la date "maximale"
min_date = times.min()
max_date = times.max()
# Présenter le résultat
print (
"""
En se basant sur les données récoltées entre {} et {},
l'heure idéale médiane pour avoir une température comprise
entre {}°C et {}°C est: {} entre le {} et le {}
""".format(
params[
"date_debut_mesure"
],
params[
"date_fin_mesure"
],
min_temp,
max_temp,
perfect_hour.strftime(
"%Hh%M"
),
min_date.strftime(
"%d %B"
),
max_date.strftime(
"%d %B"
)
)
)
En se basant sur les données récoltées entre 2015-01-01 et 2015-12-31,
l'heure idéale médiane pour avoir une température comprise
entre 11.5°C et 12°C est: 17h00 entre le 28 juin et le 30 août
Nous savons désormais quand il faut nous rendre à la station choisie pour obtenir la température idéale que nous avons déterminée.
Néanmoins, cela ne suffit pas car nous avons besoin de connaître les chemins pédestres (de randonnée) dans la commune ainsi que les points d’eau potable.
Récupérer les chemins et les points d’eau#
Pour obtenir ces données, il est nécessaire de déterminer une zone de recherche (bounding box).
Obtenir la bounding box de la commune#
Pour ce faire, utilisons une autre API, l’API Geo, toujours avec la classe Python GdfFromApi (nous pourrions utiliser l’outil Tile Calculator une nouvelle fois mais il s’agit ici d’explorer rapidement une autre API pour multiplier les exemples).
from api_request.request import GdfFromApi
# URL de base
base_url = "https://geo.api.gouv.fr"
# Complément URL
sub_url = "/communes"
# Paramètres pour la requête sous forme de dictionnaire
params = {
"nom":gdf_temperature["libelle_commune"][0],
"fields":"code,bbox",
"format":"geojson",
"geometry":"bbox"
}
# Instanciation de la classe
get_api = GdfFromApi(base_url)
# Exécuter la requête pour récupérer un GeoDataFrame
# des résultats
gdf_commune = get_api.get(
sub_url,
params
)
print (
"Status code: {}".format(
get_api.status_code
)
)
# Cartographie de la bounding box
gdf_commune.explore(
tiles="Stamen Terrain"
)
Status code: 200
Récupérer les points d’eau et les chemins pédestres dans la bounding box de la commune#
AVERTISSEMENT: nous récupérons des données pour effectuer des tests et présenter des exemples, ne vous basez par sur celles-ci pour prévoir une expédition en montagne !
Une classe Python GetNodeWay a été créée. Elle utilise l’API d’Overpass qui permet d’effectuer des requêtes afin d’obtenir des données d’OpenStreetMap et de retourner un GeoDataFrame. Cette classe fait partie du module api_request.request.
Nous avons besoin:
d’une bounding box
de tags OpenStreetMap, donc d’un ensemble de clés / valeurs
Nous devons:
déterminer la bounding box depuis le polygone de la commune
trouver les tags adéquats pour obtenir les chemins pédestres
exécuter la requête pour obtenir un GeoDataFrame
séparer les points (points d’eau potable) des lignes (chemins pédestres)
cartographier:
les points d’eau potable
les chemins pédestres
la station de températures de l’eau
import folium
from api_request.request import GetNodeWay, bbox_from_poly
# Création de la bounding box
bbox = bbox_from_poly(
gdf_commune["geometry"][0]
)
# Instanciation de la classe
overpass = GetNodeWay()
# Tuple de tuples clé/valeur OSM
tags = (
("route","hiking"),
("route","foot"),
("highway","track"),
("highway","path"),
("amenity","drinking_water")
)
# Exécution de la requête
gdf = overpass.get(bbox, tags)
# Séparer les points et les lignes
points = gdf.copy().loc[
gdf["type"]=="node"
]
ways = gdf.copy().loc[
gdf["type"]=="way"
]
# Cartographier les lignes
m = ways.explore(
color="red",
tooltip=[
"tags.highway",
"tags.tracktype",
"tags.route"
],
legend=True,
name="Chemins pédestres",
tiles="Stamen Terrain"
)
# Cartographier les points
points.explore(
m=m,
marker_kwds={
"radius":5
},
color="blue",
tooltip=[
"tags.amenity",
"lat",
"lon"
],
legend=True,
name="Fontaines eau potable"
)
# Cartographier la station
station = gdf_temperature.iloc[[0]][
[
"libelle_station",
"latitude",
"longitude",
"libelle_commune",
"code_station",
"geometry"
]
]
station.explore(
m=m,
tooltip=[
"libelle_station",
"latitude",
"longitude",
"libelle_commune",
"code_station"
],
marker_kwds={
"radius":10
},
color="black",
legend=True,
name="Station température"
)
# Ajouter un contrôle des couches dans la légende
folium.LayerControl().add_to(m)
m
Licenses librairies Python / Données / API#
Données & API#
API / Données |
License / Droit d’usage |
Documentation |
|---|---|---|
Copyright, License Open Data Commons Open Database License (ODbL) |
Librairies Python#
Voir le README.




